#include "Loader.h"
#include "utility/Stream.h"
#include "utility/File.h"

#define KERNEL_ELF_ADDRESS (1024 * 1024)

extern MemoryInfoType memoryInfo;

void EnablePaging(void)
{
    // 开启分页机制需要几个步骤

    // 先定义一个简单的映射表，映射了8MB的内存空间，一个页表对应4MB的内存空间
    static uint32_t pageTable[1024] __attribute__((aligned(4096))) = {
        [0] = PDE_P | PDE_PS | PDE_W,
        [1] = PDE_P | PDE_PS | PDE_W,
    };

    // cr4中设置分页单位
    uint32_t cr4 = ReadCr4();
    WriteCr4(cr4 | CR4_PSE); // 设置PSE允许使用4MB大页
    
    // cr3中写入页表地址
    WriteCr3((uint32_t)pageTable);
    
    // 设置cr0的分页位
    WriteCr0(ReadCr0() | CR0_PG);
}


/// @brief Handle kernel data read from disk
/// @param loadAddress 
/// @return return the address of kernel entry
uint32_t HandleKernelElf(uint32_t loadAddress)
{
    Elf32_Ehdr* elfHead = (Elf32_Ehdr*)loadAddress;
    uint8_t* fileBuffer = (uint8_t*)loadAddress;

    // 检验是不是头文件
    
        // 是有效的 ELF 文件头
    if (elfHead->e_ident[0] == ELF_IDENTIFY &&
        elfHead->e_ident[1] == 'E' &&
        elfHead->e_ident[2] == 'L' &&
        elfHead->e_ident[3] == 'F') {
            
        // The Program header is used to instruct the OS
        // how to load each segment in the elf file into memory.
        for (int i = 0; i < elfHead->e_phnum; i++)
        {
            Elf32_Phdr* programHead = (Elf32_Phdr*)(fileBuffer + elfHead->e_phoff) + i;
            if (programHead->p_type == PT_LOAD)
            {
                // data address in memory
                uint8_t* source = fileBuffer + programHead->p_offset;
                // because Paging has not been enabled,
                // the target location of the data is using the physical address.
                // virtual address is equal physical address.
                uint8_t* destination = (uint8_t*)(programHead->p_paddr);
                for (int j = 0; j < programHead->p_filesz; j++)
                {
                    *destination++ = *source++;
                }

                // set the excess memory address to 0x00
                destination = (uint8_t*)(programHead->p_paddr + programHead->p_filesz);
                uint32_t offset = programHead->p_memsz - programHead->p_filesz;
                for (int j = 0; j < offset; j++)
                {
                    *destination++=0;
                }
            }
        }

        // physical address
        return elfHead->e_entry;
        
    } else {
        // this is not elf header
        return 0;
    }
}


/// @brief this function is called in Loader.S
/// @param  
void BeforeExecuteKernel(void)
{
    uint32_t cr0 = ReadCr0();
    if (!(cr0 & 1))
    {
        return;
    }
    

    // Place the data at the target address.
    LoadDataFromDisk(50, 500, KERNEL_ELF_ADDRESS);
    uint32_t entry = HandleKernelElf(KERNEL_ELF_ADDRESS);
    if (entry==0)
    {
        return;
    }
    
    EnablePaging();

    // jmp to Origin.S
    ((void (*)(MemoryInfoType *))entry)(&memoryInfo);
    for(;;){}
}